package model;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import model.environment.StaticInfo;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.filter.ElementFilter;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.jdom.transform.JDOMSource;
import org.xml.sax.SAXException;

public class XML {
	/**
	 * Gets a Document build from XML at an URL.
	 * @param strURL The XML files URL.
	 * @return A Document build from the XML at the specified URL.
	 */
	private static Document getXMLDocument(String strURL) throws MalformedURLException, JDOMException, IOException {
		URL url = new URL(strURL);
		
		SAXBuilder builder = new SAXBuilder();
		return builder.build(url);
	}
	
	/**
	 * Gets the namespace at http://www.cs.au.dk/dWebTek/2011.
	 * @return The namespace at http://www.cs.au.dk/dWebTek/2011.
	 */
	private static Namespace getNamespace() {
		return Namespace.getNamespace("http://www.cs.au.dk/dWebTek/2011");
	}
	
	/**
	 * Gets the shops from http://services.brics.dk/java4/cloud/listShops.
	 * @return A list of shops.
	 */
	public static List<Shop> getShops() throws MalformedURLException, JDOMException, IOException {
		List<Shop> result = new ArrayList<Shop>();
		
		Document d = getXMLDocument("http://services.brics.dk/java4/cloud/listShops");
		
		Namespace w = getNamespace();
		
		Iterator<?> i = d.getDescendants(new ElementFilter("shop", w));
		
		while (i.hasNext()) {
			Element shop = (Element) i.next();
			int shopID = Integer.parseInt(shop.getChildText("shopID", w));
			String shopName = shop.getChildText("shopName", w);
			String shopURL = shop.getChildText("shopURL", w);
			
			result.add(new Shop(shopID, shopName, shopURL));
		}
		
		return result;
	}
	
	/**
	 * Gets a shop's items http://services.brics.dk/java4/cloud/listItems?shopID=x where x is the parameter shopID.
	 * @param shopID The shop's ID.
	 * @return A list of items.
	 */
	public static List<Item> getShopItems(int shopID) throws MalformedURLException, JDOMException, IOException {
		List<Item> result = new ArrayList<Item>();
		
		Document d = getXMLDocument("http://services.brics.dk/java4/cloud/listItems?shopID=" + shopID);
		
		Namespace w = getNamespace();
		
		Iterator<?> i = d.getDescendants(new ElementFilter("item", w));
		
		while (i.hasNext()) {
			Element item = (Element) i.next();
			int itemID = Integer.parseInt(item.getChildText("itemID", w));
			String itemName = item.getChildText("itemName", w);
			String itemURL = item.getChildText("itemURL", w);
			int itemPrice = Integer.parseInt(item.getChildText("itemPrice", w));
			int itemStock = Integer.parseInt(item.getChildText("itemStock", w));
			String itemDescription = item.getChildText("itemDescription", w);
			
			result.add(new Item(itemID, itemName, itemURL, itemPrice, itemStock, itemDescription));
		}
			
		return result;
	}
	
	/**
	 * Gets the cloud's customers from http://services.brics.dk/java4/cloud/listCustomers.
	 * @return A list of customers.
	 */
	public static List<Customer> getCustomers() throws MalformedURLException, JDOMException, IOException {
		List<Customer> result = new ArrayList<Customer>();
	
		Document d = getXMLDocument("http://services.brics.dk/java4/cloud/listCustomers");
		
		Namespace w = getNamespace();
		
		Iterator<?> i = d.getDescendants(new ElementFilter("customer", w));
		
		while (i.hasNext()) {
			Element customer = (Element) i.next();
			
			int customerID = Integer.parseInt(customer.getChildText("customerID", w));
			String customerName = customer.getChildText("customerName", w);
			
			result.add(new Customer(customerID, customerName));
		}
		
		return result;
	}
	
	/**
	 * Gets a customer's sales from http://services.brics.dk/java4/cloud/listCustomerSales?customerID=x where x is a customer's ID.
	 * @param customerID The customer's ID.
	 * @return A list of sales by a customer.
	 */
	public static List<Sale> getCustomerSales(int customerID) throws MalformedURLException, JDOMException, IOException {
		List<Sale> result = new ArrayList<Sale>();
		
		Document d = getXMLDocument("http://services.brics.dk/java4/cloud/listCustomerSales?customerID=" + customerID);
		
		Namespace w = getNamespace();
		
		Iterator<?> i = d.getDescendants(new ElementFilter("sale", w));
		
		while (i.hasNext()) {
			Element sale = (Element) i.next();
			
			long saleTime = Long.parseLong(sale.getChildText("saleTime", w));
			int saleAmount = Integer.parseInt(sale.getChildText("saleAmount", w));
			int shopID = Integer.parseInt(sale.getChildText("shopID", w));
			int itemID = Integer.parseInt(sale.getChildText("itemID", w));
			int newCustomerID = Integer.parseInt(sale.getChildText("customerID", w));
			int saleItemPrice = Integer.parseInt(sale.getChildText("saleItemPrice", w));
			
			result.add(new Sale(saleTime, saleAmount, shopID, itemID, newCustomerID, saleItemPrice));
		}
		
		return result;
	}
	
	/**
	 * Get a shop's sales from http://services.brics.dk/java4/cloud/listShopSales?shopID=x where x is a shop's ID.
	 * @param shopID The shop's ID.
	 * @return A list of sales from a specific shop.
	 */
	public static List<Sale> getShopSales() throws MalformedURLException, JDOMException, IOException {
		List<Sale> result = new ArrayList<Sale>();
	
		Document d = getXMLDocument("http://services.brics.dk/java4/cloud/listShopSales?shopKey=" + StaticInfo.SHOP_KEY);
		
		Namespace w = getNamespace();
		
		Iterator<?> i = d.getDescendants(new ElementFilter("sale", w));
		
		while (i.hasNext()) {
			Element sale = (Element) i.next();
			
			long saleTime = Long.parseLong(sale.getChildText("saleTime", w));
			int saleAmount = Integer.parseInt(sale.getChildText("saleAmount", w));
			int newShopID = Integer.parseInt(sale.getChildText("shopID", w));
			int itemID = Integer.parseInt(sale.getChildText("itemID", w));
			int customerID = Integer.parseInt(sale.getChildText("customerID", w));
			int saleItemPrice = Integer.parseInt(sale.getChildText("saleItemPrice", w));
			
			result.add(new Sale(saleTime, saleAmount, newShopID, itemID, customerID, saleItemPrice));
		}
		
		return result;
	}
	
	/**
	 * Gets the customer from a login response based on a string response.
	 * @param response The response.
	 * @return The newly logged in customer.
	 */
	public static Customer getLoginResponse(Document response) throws JDOMException, IOException, NumberFormatException {
		Namespace w = getNamespace();
		
		int id = Integer.parseInt(response.getRootElement().getChildText("customerID", w));
		String name = response.getRootElement().getChildText("customerName", w);
		
		return new Customer(id, name);
	}
	
	/**
	 * Gets the customer id from a createUserResponse.
	 * @param response The response.
	 * @return The customer id from the create user response.
	 */
	public static int getCreateUserResponseCustomerID(Document response) throws JDOMException, IOException, NumberFormatException {
		Namespace w = getNamespace();
		
		int id = Integer.parseInt(response.getRootElement().getChildText("customerID", w));
		
		return id;
	}
	
	/**
	 * Checks if a checkout of an item was successful base on the response from the server.
	 * @param response The server's response to the item checkout.
	 * @return True if successful, false if not.
	 */
	public static boolean checkoutItemSuccessfull(Document response) {
		Element okElement = response.getRootElement().getChild("ok", getNamespace());
		
		if (okElement != null)
			return true;
		
		return false;
	}
	
	/**
	 * Returns the ID of a newly created ID based on the response from the server.
	 * @param response The response from the server.
	 * @return The new item's ID or -1.
	 */
	public static int getCreatedItemID(Document response) {
		try {
			return Integer.parseInt(response.getRootElement().getText());
		} catch (Exception e) {
			return -1;
		}
	}
	
	/**
	 * Posts an XML document to an URL.
	 * @param d The XML document.
	 * @param url The URL.
	 * @return The response from the URL.
	 */
	public static Document postXML(Document d, String url) throws MalformedURLException, IOException, JDOMException {
		URL query = new URL(url);
		
		URLConnection conn = query.openConnection();
		
		conn.setDoInput(true);
		conn.setDoOutput(true);
		conn.setConnectTimeout(10000);
		conn.setReadTimeout(10000);
	    conn.setRequestProperty("Content-Type", "text/xml");
		
		XMLOutputter xmlOut = new XMLOutputter(Format.getRawFormat());
		xmlOut.output(d, conn.getOutputStream());
		
		SAXBuilder b = new SAXBuilder();
		Document response = b.build(conn.getInputStream());
		
		return response;
	}
	
	/**
	 * Creates a login element consisting of a customerName element and a customerPass element.
	 * @param username The text of the customerName element.
	 * @param password The text of the customerPass element.
	 * @return A document with the login element.
	 */
	public static Document createLoginElement(String username, String password) {
		Namespace w = getNamespace();
		
		Element login = new Element("login", w);
		
		Element name = new Element("customerName", w);
		name.setText(username);
		login.addContent(name);
		
		Element pass = new Element("customerPass", w);
		pass.setText(password);
		login.addContent(pass);
		
		return new Document(login);
	}
	
	/**
	 * Creates a modifyItem element from an item and a shop key.
	 * @param item The item.
	 * @param shopKey The shop key.
	 * @return A document with the modifyItem element.
	 */
	public static Document createModifyItemElement(Item item, String shopKey) {
		Namespace w = getNamespace();
		
		Element modifyItem = new Element("modifyItem", w);
		
		Element key = new Element("shopKey", w);
		key.setText(shopKey);
		modifyItem.addContent(key);
		
		Element itemID = new Element("itemID", w);
		itemID.setText("" + item.getItemID());
		modifyItem.addContent(itemID);
		
		Element itemName = new Element("itemName", w);
		itemName.setText(item.getItemName());
		modifyItem.addContent(itemName);
		
		Element itemPrice = new Element("itemPrice", w);
		itemPrice.setText("" + item.getItemPrice());
		modifyItem.addContent(itemPrice);
		
		Element itemURL = new Element("itemURL", w);
		itemURL.setText(item.getItemURL());
		modifyItem.addContent(itemURL);
		
		Element itemDescription = new Element("itemDescription", w);
		itemDescription.setText(item.getItemDescription());
		modifyItem.addContent(itemDescription);
		
		return new Document(modifyItem);
	}
	
	/**
	 * Creates a sellItems element from a sale and a shop key.
	 * @param sale The sale.
	 * @param shopKey The shop key.
	 * @return A document with the sellItems element.
	 */
	public static Document createSellItemsElement(Sale sale, String shopKey) {
		Namespace w = getNamespace();
		
		Element sellItems = new Element("sellItems", w);
		
		Element key = new Element("shopKey", w);
		key.setText(shopKey);
		sellItems.addContent(key);
		
		Element itemID = new Element("itemID", w);
		itemID.setText("" + sale.getItemID());
		sellItems.addContent(itemID);
		
		Element customerID = new Element("customerID", w);
		customerID.setText("" + sale.getCustomerID());
		sellItems.addContent(customerID);
		
		Element saleAmount = new Element("saleAmount", w);
		saleAmount.setText("" + sale.getSaleAmount());
		sellItems.addContent(saleAmount);
		
		return new Document(sellItems);
	}
	
	/**
	 * Creates a createItem element from an item name and a shop key.
	 * @param itemName The new item's name.
	 * @param shopKey The shop key.
	 * @return A document with the createItem element.
	 */
	public static Document createCreateItemElement(String itemName, String shopKey) {
		Namespace w = getNamespace();
		
		Element createItem = new Element("createItem", w);
		
		Element name = new Element("itemName", w);
		name.setText(itemName);
		createItem.addContent(name);
		
		Element key = new Element("shopKey", w);
		key.setText(shopKey);
		createItem.addContent(key);
		
		return new Document(createItem);
	}
	
	/**
	 * Creates a createCustomer element from a name, password and a shop key.
	 * @param customerName The new customer's name.
	 * @param customerPass The new customer's password.
	 * @param shopKey The shop key.
	 * @return A document with the createCustomer element.
	 */
	public static Document createCreateCustomerElement(String customerName, String customerPass, String shopKey) {
		Namespace w = getNamespace();
		
		Element createCustomer = new Element("createCustomer", w);
		
		Element key = new Element("shopKey", w);
		key.setText(shopKey);
		createCustomer.addContent(key);
		
		Element name = new Element("customerName", w);
		name.setText(customerName);
		createCustomer.addContent(name);
		
		Element pass = new Element("customerPass", w);
		pass.setText(customerPass);
		createCustomer.addContent(pass);
		
		return new Document(createCustomer);
	}
	
	/**
	 * Creates an adjustItemStock element from a shop key, an item id and an adjustment integer.
	 * @param shopKey The shop key.
	 * @param itemID The item's id.
	 * @param adjustment The item's adjustment.
	 * @return A document with the adjustItemStock element.
	 */
	public static Document createAdjustItemStockElement(String shopKey, int itemID, int adjustment) {
		Namespace w = getNamespace();
		
		Element adjustItemStock = new Element("adjustItemStock", w);
		
		Element key = new Element("shopKey", w);
		key.setText(shopKey);
		adjustItemStock.addContent(key);
		
		Element id = new Element("itemID", w);
		id.setText("" + itemID);
		adjustItemStock.addContent(id);
		
		Element adj = new Element("adjustment", w);
		adj.setText("" + adjustment);
		adjustItemStock.addContent(adj);
		
		return new Document(adjustItemStock);
	}
	
	/**
	 * Attempts to validate an XML document. 
	 * @deprecated Not recommended, slow and doesn't even attempt to validate an itemDescription (because all "<" and ">" are escaped). 
	 * @param d The XML to validate.
	 * @param xsdPath The path to the XML schema file.
	 * @throws SAXException If something goes wrong validating the XML = invalid XML.
	 * @throws IOException If something goes wrong, loading the source.
	 */
	@Deprecated 
	public static void validateXML(Document d, String xsdPath) throws SAXException, IOException {
		Source s = new JDOMSource(d);
		
		Validator v = loadSchema(xsdPath).newValidator();
		
		v.validate(s);
	}
	
	/**
	 * Gets an XML schema from a specified path.
	 * @param xsdPath The XML schema's path.
	 * @return The XML schema.
	 * @throws SAXException If something goes wrong while loading the file.
	 * @throws MalformedURLException If xsdPath is malformed.
	 * @throws URISyntaxException If xsdPath is not a valid URI.
	 */
	private static Schema loadSchema(String xsdPath) throws SAXException, MalformedURLException {
		Schema schema = null;
		// Getting the default implementation of XML Schema factory
		String language = XMLConstants.W3C_XML_SCHEMA_NS_URI;
		SchemaFactory factory = SchemaFactory.newInstance(language);
		
		schema = factory.newSchema(new File(xsdPath));

		return schema;
	}
	
	/**
	 * Appends the XHTML namespace (h:) to the valid XHTML-tags in an item description.
	 * For example, &ltb&gt becomes &lth:b&gt and &lti&gt becomes &lth:i&gt.
	 * @deprecated Was used with validateXML, but since this is no more than an ugly attempt to make it work, it's not recommended to use. 
	 * @param itemDescription The item description.
	 */
	@Deprecated 
	public static String appendNamespaceToItemDescription(String itemDescription) {
		// Just "bruteforce" it, an alternative could be a regular expression.
		String result = itemDescription;
		
		result = result.replace("<b>", "<h:b>");
		result = result.replace("</b>", "</h:b>");
		result = result.replace("<i>", "<h:i>");
		result = result.replace("</i>", "</h:i>");
		result = result.replace("<span>", "<h:span>");
		result = result.replace("</span>", "</h:span>");
		result = result.replace("<ul>", "<h:ul>");
		result = result.replace("</ul>", "</h:ul>");
		result = result.replace("<li>", "<h:li>");
		result = result.replace("</li>", "</h:li>");
		
		return result;
	}
	
	/**
	 * Removes the XHTML namespace (h:) to the valid XHTML-tags in an item description.
	 * This is nescessary in showItem.jsp 
	 * For example, &lth:b&gt becomes &ltb&gt and &lth:i&gt becomes &lti&gt.
	 * @deprecated see appendNamespaceToItemDescription(). 
	 * @param itemDescription The item description.
	 */
	public static String removeNamespaceToItemDescription(String itemDescription) {
		// Just "bruteforce" it, an alternative could be a regular expression.
		String result = itemDescription;
		
		result = result.replace("<h:b>", "<b>");
		result = result.replace("</h:b>", "</b>");
		result = result.replace("<h:i>", "<i>");
		result = result.replace("</h:i>", "</i>");
		result = result.replace("<h:span>", "<span>");
		result = result.replace("</h:span>", "</span>");
		result = result.replace("<h:ul>", "<ul>");
		result = result.replace("</h:ul>", "</ul>");
		result = result.replace("<h:li>", "<li>");
		result = result.replace("</h:li>", "</li>");
		
		return result;
	}
}
